﻿namespace MefContrib.Models.Provider
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// 
    /// </summary>
    public static class TypeExtensions
    {
        /// <summary>
        /// Checks if the type is enumerable.
        /// </summary>
        /// <param name="source">The type to inspect.</param>
        /// <returns><see langword="true" /> if the type is enumerable; otherwise <see langword="false" />.</returns>
        public static bool IsEnumerable(this Type source)
        {
            if (source == typeof(string))
                return false;

            return source == typeof(IEnumerable) || source.GetInterfaces().Contains(typeof (IEnumerable));
        }

        /// <summary>
        /// Checks if the type is of a known lazy type.
        /// </summary>
        /// <param name="source">The type to inspect.</param>
        /// <returns><see langword="true" /> if <paramref name="source"/> is of a known lazy type; otherwise <see langword="false" />.</returns>
        public static bool IsLazyType(this Type source)
        {
            if (_knownLazyTypes == null)
            {
                _knownLazyTypes = new[] {typeof (Lazy<>), typeof (Lazy<,>)};
            }

            while(!source.IsGenericType && source.BaseType != null)
                source = source.BaseType;
            
            if (!source.IsGenericType) return false;

            var genericTypeDefinition = source.GetGenericTypeDefinition();

            source = genericTypeDefinition.IsAssignableFrom(typeof (IEnumerable<>))
                         ? source.GetGenericArguments().FirstOrDefault().GetGenericTypeDefinition()
                         : genericTypeDefinition;
             
            return _knownLazyTypes.Any(known => known.IsAssignableFrom(source));
        }

        /// <summary>
        /// Creates an export object type from the specified type.
        /// </summary>
        /// <param name="source">The type for which the export type should be created.</param>
        /// <returns>A <see cref="Lazy{TContract,TMetadata}"/> type.</returns>
        public static Type ToLazyType(this Type source)
        {
            if (!source.IsLazyType())
            {
                return null;
            }

            Type[] arguments =
                source.GetGenericArguments();

            Type contractType = (arguments.Length > 0) ?
                arguments[0] : typeof(object);

            Type metadataType = (arguments.Length > 1) ?
                arguments[1] : typeof(IDictionary<string, object>);

            return typeof(Lazy<,>).MakeGenericType(contractType, metadataType);
        }

        /// <summary>
        /// Creates an export collection object type from the specified type.
        /// </summary>
        /// <param name="source">The type for which the export collection type should be created.</param>
        /// <returns>Either an IEnumerable<Lazy<object>> or IEnumerable<Lazy<T,TMetadata>>  depending on the best match.</returns>
        public static Type ToLazyCollectionType(this Type source)
        {
            if (!source.IsLazyType())
            {
                return null;
            }

            Type[] arguments =
                source.GetGenericArguments();

            Type contractType = (arguments.Length > 0) ?
                arguments[0] : null;

            Type metadataType = (arguments.Length > 1) ?
                arguments[1] : null;

            if (contractType == null)
            {
                return typeof(List<Lazy<object>>);
            }

            return (metadataType == null) ?
                typeof(List<>).MakeGenericType(typeof(Lazy<>).MakeGenericType(contractType)) :
                typeof(List<>).MakeGenericType(typeof(Lazy<,>).MakeGenericType(contractType, metadataType));
        }

        private static Type[] _knownLazyTypes;
    }
}